package org.example;

import lombok.Data;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;

import static org.springframework.cglib.proxy.Mixin.createBean;

public class MyAnnotationConfigApplicationContext {

    protected Class configClass;
    protected Map<String,BeanDefinition> beanDefinitionMap = new LinkedHashMap<>();
    protected Map<String,Object> singletonObjects = new LinkedHashMap<>();

    public MyAnnotationConfigApplicationContext(Class configClass){
        try{
            this.configClass = configClass;
            if (!configClass.isAnnotationPresent(Configuration.class)) {
                throw new RuntimeException("该类不是Spring配置类");
            }
            // IOC容器初始化流程
            // 查找 Bean, 创建 BeanDefinitionMap 容器
            // Bean方法
            scanBeanDefinitionByMethod();

            // 包扫描
            scanBeanDefinitionByScan();

//            System.out.println("-------------- scanBeanDefinitionByScan beanDefinitionMap ---------------");
//            beanDefinitionMap.forEach((name,beanDefinition)->{
//                System.out.println(name + ":" + beanDefinition);
//            });

            // 创建 Bean 存入 singletonObjects(单例池) 中
            createBean();

            // TODO 请扩展完成 自动注入, 遍历所有的bean的属性, 如果有Autowried注解,
            // TODO 则通过反射从容器中获取bean 注入到bean属性中
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void scanBeanDefinitionByScan() throws UnsupportedEncodingException, ClassNotFoundException {
        ComponentScan componentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
        if(componentScan == null) return;
        // 获取包路径
        String[] packages = componentScan.value();
        // 创建根包路径 classes
        URL classesURL = configClass.getClassLoader().getResource("");
        String classesPath = classesURL.getPath();
        classesPath = URLDecoder.decode(classesPath, "utf-8");
        classesPath = classesPath.replace("test-classes", "classes");
        File classes = new File(classesPath);
        // 遍历包
        for (String aPackage : packages) {
            // 构建扫描的包路径
            String path = aPackage.replaceAll("\\.","/");
            URL url = configClass.getClassLoader().getResource(path);
            String dirPath = url.getPath();
            // 转中文
            dirPath = URLDecoder.decode(dirPath, "UTF-8");
            // 替换编译目录
            dirPath = dirPath.replace("test-classes","classes");
            File dir =  new File(dirPath);
            // 调用递归函数
            scanBeanDefinitionByScan(classes,dir);
        }
    }

    private void scanBeanDefinitionByScan(File rootDir, File dir) throws ClassNotFoundException {
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                scanBeanDefinitionByScan(rootDir,file);
            } else if(file.getName().endsWith(".class")){
                // TODO ...
                String fileAbsolutePath = file.getAbsolutePath();
                String rootDirAbsolutePath = rootDir.getAbsolutePath();
                // 掐头
                String classPath = fileAbsolutePath.replace(rootDirAbsolutePath,"");
                // 去尾
                classPath = classPath.replace(".class", "");
                // 去掉第一个 \
                classPath = classPath.substring(1);
                // 替换 \ 为 .
                classPath = classPath.replaceAll("\\\\", ".");
                Class<?> beanClass = Class.forName(classPath);
                if(beanClass.isAnnotationPresent(Service.class)||
                        beanClass.isAnnotationPresent(Repository.class)||
                        beanClass.isAnnotationPresent(Controller.class)||
                        beanClass.isAnnotationPresent(Component.class)){
                    BeanDefinition bd = new BeanDefinition();
                    bd.setBeanClass(beanClass);
                    bd.setBeanClassName(beanClass.getName());
                    if(beanClass.isAnnotationPresent(Scope.class)){
                        bd.setScope(beanClass.getAnnotation(Scope.class).value());
                    }
                    if(beanClass.isAnnotationPresent(Primary.class)){
                        bd.setPrimary(true);
                    }
                    if(beanClass.isAnnotationPresent(Lazy.class)){
                        bd.setLazyInit(true);
                    }
                    String name = null;
                    Service service = (Service) beanClass.getAnnotation(Service.class);
                    if(service != null){
                        name = service.value();
                    }
                    Repository repository = (Repository) beanClass.getAnnotation(Repository.class);
                    if(repository != null){
                        name = repository.value();
                    }
                    Controller controller = (Controller) beanClass.getAnnotation(Controller.class);
                    if(controller != null){
                        name = controller.value();
                    }
                    Component component = (Component) beanClass.getAnnotation(Component.class);
                    if(component != null){
                        name = component.value();
                    }
                    // 如果没有设置名字, 那么使用小字母开头的类名作为name
                    if(name.isBlank()){
                        name = beanClass.getSimpleName();
                        name = name.substring(0,1).toLowerCase() + name.substring(1);
                    }
                    beanDefinitionMap.put(name,bd);
                }
            }
        }
    }

    private void scanBeanDefinitionByMethod() {
        for (Method method : configClass.getDeclaredMethods()) {
            if(method.isAnnotationPresent(Bean.class)){
                BeanDefinition bd = new BeanDefinition();
                bd.setBeanClass(method.getReturnType());
                bd.setBeanClassName(method.getReturnType().getName());
                if(method.isAnnotationPresent(Scope.class)){
                    bd.setScope(method.getAnnotation(Scope.class).value());
                }
                if(method.isAnnotationPresent(Primary.class)){
                    bd.setPrimary(true);
                }
                String[] names = method.getAnnotation(Bean.class).value();
                String name;
                if(names.length == 0 || names[0].isEmpty()){
                    name = method.getName();
                } else {
                    name = names[0];
                }
                if(method.isAnnotationPresent(Lazy.class)){
                    bd.setLazyInit(true);
                }
                // 记录 Bean 方法
                bd.setBeanMethod(method);
                beanDefinitionMap.put(name,bd);
            }
        }
    }

    private void createBean() throws InstantiationException, IllegalAccessException {
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition bd = entry.getValue();
            if(bd.isLazyInit() || bd.getScope().equals("prototype")){
                // 懒加载 和 原型模式 不在 IOC 容器创建时, 创建bean
                continue;
            }
            // 默认 bean 类提供了无参数构造函数
            try{
                Object bean = createBean(bd);
                singletonObjects.put(beanName,bean);
                // TODO bean 的 初始化 的方法的执行
            } catch (Exception e){
                System.out.println(e.getMessage());
            }
        }

    }


    public <T> T getBean(Class<T> cls){
        Map<String,T> beans = new HashMap<>();
        singletonObjects.forEach((name, bean) -> {
            // 判断是否是该类的bean
           if(bean.getClass().isAssignableFrom(cls)){
               beans.put(name, (T) bean);
           }
        });
        if(beans.size() == 0){
            throw new RuntimeException("没有该Bean: " + cls);
        } else if(beans.size() == 1){
            return beans.values().iterator().next();
        } else {
            T ret = null;
            // 遍历查找 主bean
            for (Map.Entry<String, T> entry : beans.entrySet()) {
                String name = entry.getKey();
                T bean = entry.getValue();
                BeanDefinition bd = beanDefinitionMap.get(name);
                if(bd.isPrimary()){
                    ret = bean;
                    break;
                }
            }
            return ret;
        }
    }

    public Object getBean(String name){
        Object bean = singletonObjects.get(name);
        if(bean == null){
            BeanDefinition bd = beanDefinitionMap.get(name);
            if (bd == null) {
                throw new RuntimeException("没有该Bean: " + name);
            }
            if (bd.getScope().equals("prototype")) {
                // 原型模式, 每次getBean 都要创建新的对象
                // 创建 bean , 但是 bean 不存入 ioc 容器 singletonObjects;
                return createBean(bd);
            } else if(bd.isLazyInit()) {
                // 第一次geBean 创建bean
                // 创建 bean , 将 bean 存入 ioc 容器 singletonObjects;
                bean = createBean(bd);
                singletonObjects.put(name,bean);
                return bean;
            }

        }
        return bean;
    }

    private Object createBean(BeanDefinition bd) {
        try{
            Object bean = null;
            if (bd.getBeanMethod() == null) {
                // 包扫描到的组件 创建 bean
                bean = bd.getBeanClass().newInstance();
            } else {
                // TODO @Bean 方法 创建 bean 的方式 未实现...
                Object config = configClass.newInstance();
                Method beanMethod = bd.getBeanMethod();
                beanMethod.setAccessible(true);
                bean = beanMethod.invoke(config);
            }
            return bean;
        } catch (Exception e){
            throw new RuntimeException("创建bean失败" + bd.getBeanClass(), e);
        }
    }

    public void close(){

    }
}

@Data
class BeanDefinition {
    private String beanClassName;
    private Class<?> beanClass;
    private String scope = "singleton";
    private boolean lazyInit = false;
    private boolean primary = false;
    private String[] dependsOn;
    private boolean autowireCandidate = true;
    private String initMethodName;
    private String destroyMethodName;
    private String factoryMethodName;
    private String factoryBeanName;

    // @Bean 的方法
    private Method beanMethod;

}